package top.heue.utils.compose

import java.awt.EventQueue
import java.awt.Frame
import java.awt.Window
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.io.RandomAccessFile
import java.net.InetAddress
import java.net.ServerSocket
import java.net.Socket
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.nio.channels.OverlappingFileLockException
import java.nio.charset.StandardCharsets
import kotlin.concurrent.thread

object SingleInstanceApp {
    private var appId: String = ""
    private var appDir: File? = null
    private var lockFile: File? = null
    private var infoFile: File? = null
    private var singleInstanceLock: FileLock? = null
    private var singleInstanceChannel: FileChannel? = null
    private var singleInstanceServer: ServerSocket? = null
    private var mainWindow: Window? = null

    private fun resolveAppDir(id: String): File {
        val appData = System.getenv("APPDATA")
        return if (!appData.isNullOrBlank()) {
            File(appData, id)
        } else {
            val home = System.getProperty("user.home") ?: "."
            File(home, id)
        }
    }

    fun acquireSingleInstance(id: String): Boolean {
        appId = id
        val dir = resolveAppDir(appId)
        appDir = dir
        lockFile = File(dir, ".lock")
        infoFile = File(dir, ".lockinfo")
        return try {
            dir.mkdirs()
            singleInstanceChannel = RandomAccessFile(lockFile, "rw").channel
            singleInstanceLock = singleInstanceChannel?.tryLock()
            if (singleInstanceLock != null) {
                Runtime.getRuntime().addShutdownHook(Thread {
                    try { singleInstanceServer?.close() } catch (_: Throwable) {}
                    try { singleInstanceLock?.release() } catch (_: Throwable) {}
                    try { singleInstanceChannel?.close() } catch (_: Throwable) {}
                })
                startIpcServer()
                true
            } else false
        } catch (_: OverlappingFileLockException) {
            false
        } catch (_: Throwable) {
            false
        }
    }

    fun attemptWakeExisting(id: String) {
        val dir = resolveAppDir(id)
        val info = runCatching { File(dir, ".lockinfo").readText(StandardCharsets.UTF_8) }.getOrNull() ?: return
        val line = info.trim()
        val parts = line.split(":")
        if (parts.size < 2) return
        val host = parts[0]
        val port = parts[1].toIntOrNull() ?: return
        runCatching {
            Socket(host, port).use { sock ->
                BufferedWriter(OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8)).use { w ->
                    w.write("SHOW_MAIN\n")
                    w.flush()
                }
            }
        }
    }

    fun startIpcServer() {
        if (singleInstanceServer != null) return
        val ss = ServerSocket(0, 50, InetAddress.getByName("127.0.0.1"))
        singleInstanceServer = ss
        val dir = appDir ?: resolveAppDir(appId)
        dir.mkdirs()
        val lf = File(dir, ".lockinfo")
        lf.writeText("127.0.0.1:" + ss.localPort, StandardCharsets.UTF_8)
        infoFile = lf

        thread(isDaemon = true, name = "SingleInstanceIpc") {
            while (!ss.isClosed) {
                try {
                    val s = ss.accept()
                    BufferedReader(InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)).use { r ->
                        val line = r.readLine() ?: ""
                        if (line.trim() == "SHOW_MAIN") {
                            EventQueue.invokeLater {
                                bringMainToFront()
                            }
                        }
                    }
                    runCatching { s.close() }
                } catch (_: Exception) {
                    if (ss.isClosed) break
                }
            }
        }
        Runtime.getRuntime().addShutdownHook(Thread {
            try {
                singleInstanceServer?.close()
            } catch (_: Throwable) {
            }
            try {
                infoFile?.let { if (it.exists()) infoFile?.delete() }
            } catch (_: Throwable) {
            }
        })
    }

    fun registerMainWindow(window: Window) {
        mainWindow = window
    }

    fun stop() {
        try { singleInstanceServer?.close() } catch (_: Throwable) {}
        singleInstanceServer = null
        try { singleInstanceLock?.release() } catch (_: Throwable) {}
        singleInstanceLock = null
        try { singleInstanceChannel?.close() } catch (_: Throwable) {}
        singleInstanceChannel = null
    }

    private fun bringMainToFront() {
        val win = mainWindow ?: Window.getWindows().firstOrNull { it.isVisible } ?: return
        (win as? Frame)?.state = Frame.NORMAL
        win.isVisible = true
        win.toFront()
        win.requestFocus()
    }
}
